// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#include "coin2/exchange/lbank/order_v2/parser.h"

namespace coin2::exchange::lbank::order_v2 {

void LbankParser::HitRateLimit(std::string func) {
  // auto enable_time = 5 * 1'000'000'000LL + GetCurrentTimestamp();
  // mb_->PublishEvent("lbank/enable_time", reinterpret_cast<void*>(enable_time));
  LOG(ERROR) << "[" + func + "] Rate Limit Exceeded\n" << msg_->DebugString();
}

std::optional<int64_t> LbankParser::GetErrorCode(const JsonValue& json) {
  std::string key;

  if (json.HasMember("error_code")) {
    key = "error_code";
  } else if (json.HasMember("code")) {
    key = "code";
  } else {
    return std::nullopt;
  }

  return stoll(json[key.c_str()].GetString());
}

std::string LbankParser::GetErrorMsg(int64_t error_code) {
  switch (error_code) {
    case 0:
      return "return success";
    case 10000:
      return "Internal error";
    case 10001:
      return "The required parameters can not be empty";
    case 10002:
      return "Validation Failed";
    case 10003:
      return "Invalid parameter";
    case 10004:
      return "Request too frequent";
    case 10005:
      return "Secret key does not exist";
    case 10006:
      return "User does not exist";
    case 10007:
      return "Invalid signature";
    case 10008:
      return "Invalid Trading Pair";
    case 10009:
      return "Price and/or Amount are required for limit order";
    case 10010:
      return "Price and/or Amount must less than minimum require";
    case 10013:
      return "The amount is too small";
    case 10014:
      return "Insufficient amount of money in account";
    case 10015:
      return "Invalid order type";
    case 10016:
      return "Insufficient account balance";
    case 10017:
      return "Server Error";
    case 10018:
      return "Page size should be between 1 and 50";
    case 10019:
      return "Cancel NO more than 3 orders in one request";
    case 10020:
      return "Volume < 0.001";
    case 10021:
      return "Price < 0.01";
    case 10022:
      return "Invalid authorization";
    case 10023:
      return "Market Order is not supported yet";
    case 10024:
      return "User cannot trade on this pair";
    case 10025:
      return "Order has been filled";
    case 10026:
      return "Order has been cancelld";
    case 10027:
      return "Order is cancelling";
    case 10028:
      return "Wrong query time";
    case 10029:
      return "'from' is not in the query time";
    case 10030:
      return "'from' do not match the transaction type of inqury";
    case 10031:
      return "echostr length must be valid and length must be from 30 to 40";
    case 10033:
      return "Failed to create order";
    case 10036:
      return "customID duplicated";
    case 10037:
      return "Order cancellation exception";
    case 10100:
      return "Has no privilege to withdraw";
    case 10101:
      return "Invalid fee rate to withdraw";
    case 10102:
      return "Too little to withdraw";
    case 10103:
      return "Exceed daily limitation of withdraw";
    case 10104:
      return "Cancel was rejected";
    case 10105:
      return "Request has been cancelled";
    case 10106:
      return "None trade time";
    case 10107:
      return "Start price exception";
    case 10108:
      return "can not create order";
    case 10109:
      return "wallet address is not mapping";
    case 10110:
      return "transfer fee is not mapping";
    case 10111:
      return "mount > 0";
    case 10112:
      return "fee is too lower";
    case 10113:
      return "transfer fee is 0";
    case 10600:
      return "intercepted by replay attacks filter, check timestamp";
    case 10601:
      return "Interface closed unavailable";
    case 10701:
      return "invalid asset code";
    case 10702:
      return "not allowed deposit";
    case 10066:
      return "Please check the chain name";
    case 10039:
      return "Order timeout cancellation";
    case 10032:
      return "The order number does not exist";
  }

  return "Exchange unknown error";
}

std::optional<bool> LbankParser::GetResult(const JsonValue& json) {
  if (!json.HasMember("result")) {
    return std::nullopt;
  }

  const auto& result = json["result"];
  if (result.IsString()) {
    return std::string(result.GetString()) == "true";
  }

  return result.GetBool();
}

coin::proto::OrderErrorCode LbankParser::GetOrderErrorCode(int64_t exchange_error_code) {
  using coin::proto::OrderErrorCode;
  auto result = OrderErrorCode::ORDER_UNKNOWN_ERROR;

  if (exchange_error_code == 0) {
    result = OrderErrorCode::ORDER_NO_ERROR;
  } else if (exchange_error_code == 10004) {
    result = OrderErrorCode::API_LIMIT_ERROR;
    HitRateLimit(__func__);
  } else if (exchange_error_code == 10016 || exchange_error_code == 10014) {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (exchange_error_code == 10025) {
    result = OrderErrorCode::ORDER_FILLED_ALREADY;
  } else if (exchange_error_code == 10026) {
    result = OrderErrorCode::ORDER_CANCELED_ALREADY;
  } else if (exchange_error_code == 10032) {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  } else {
    LOG(ERROR) << "[GetOrderErrorCode] " << exchange_error_code;
  }

  return result;
}

bool LbankParser::IsLbankSpotErrMessage(const JsonValue& doc) {
  auto error_code = GetErrorCode(doc);
  auto result = GetResult(doc);

  return !error_code || !result || error_code.value() != 0 || !result.value();
}

int64_t LbankParser::GetProcOrderId(const JsonValue& data) {
  if (!data.HasMember("clientOrderId") || !data["clientOrderId"].IsString() ||
      data["clientOrderId"].IsNull()) {
    return 0;
  }

  return stoll(data["clientOrderId"].GetString());
}

int64_t LbankParser::GetWsProcOrderId(const JsonValue& data) {
  if (!data.HasMember("customerID") || !data["customerID"].IsString() ||
      data["customerID"].IsNull()) {
    return 0;
  }

  return stoll(data["customerID"].GetString());
}

coin::proto::OrderSide LbankParser::GetOrderSide(const std::string& type) {
  if (StringStartsWith(type, "buy")) {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (StringStartsWith(type, "sell")) {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

coin::proto::OrderType LbankParser::GetOrderType(const std::string& type) {
  if (StringEndsWith(type, "market")) {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::LIMIT_ORDER;
  }
}

void LbankParser::SetOrderStatus(ProductOrderElement* order, const std::string& status) {
  if ((status == "0") || (status == "1")) {
    order->set_is_live(true);
  } else if (status == "-1" || status == "3") {
    order->set_is_live(false);
    order->set_cancel_confirmed(true);
  } else if (status == "2") {
    order->set_is_live(false);
    order->set_fully_filled(true);
  } else if (status == "4") {
    order->set_is_live(false);
  } else {
    LOG(ERROR) << "[SetOrderStatus] Unknown order status: " << status;
  }
}

bool LbankParser::IsBalanceUpdate(const JsonValue& json) {
  if (!json.HasMember("type")) {
    return false;
  }
  return json["type"] == "assetUpdate";
}

bool LbankParser::IsOrderUpdate(const JsonValue& json) {
  if (!json.HasMember("type")) {
    return false;
  }
  return json["type"] == "orderUpdate";
}

void LbankParser::ParseAccountBalance() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseAccountBalance] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsLbankSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseAccountBalance] Error message\n" << JsonToString(doc);
    return;
  }

  auto account_balance = ParseBalance(doc);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance LbankParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }
  AccountBalance acc_balance;
  acc_balance.set_exchange("Lbank");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeLower()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool LbankParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  {
    "result": "true",
    "data": [
      {
        "usableAmt": "500",
        "assetAmt": "500",
        "networkList": [
          {
            "isDefault": true,
            "withdrawFeeRate": "",
            "name": "matic",
            "withdrawMin": 1,
            "minLimit": 0.0001,
            "minDeposit": 1,
            "feeAssetCode": "usdt",
            "withdrawFee": "1",
            "type": 1,
            "coin": "usdt",
            "network": "matic"
          }
        ],
        "freezeAmt": "0",
        "coin": "usdt"
      }
    ],
    "code": 0
  }
  */
  if (IsLbankSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] Error message\n" << JsonToString(doc);
    return false;
  }

  if (!doc.HasMember("data") || !doc["data"].IsArray()) {
    LOG(ERROR) << "[ParseBalance] Invalid data format\n" << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["data"].GetArray()) {
    if (!data.HasMember("coin") || !data.HasMember("assetAmt") || !data.HasMember("usableAmt") ||
        !data.HasMember("freezeAmt")) {
      continue;
    }
    CurrencyBalance currency_balance;
    currency_balance.set_currency_native(data["coin"].GetString());
    auto total = stod(data["assetAmt"].GetString());
    auto avail = stod(data["usableAmt"].GetString());
    auto hold = stod(data["freezeAmt"].GetString());
    currency_balance.set_available(avail);
    currency_balance.set_total(total);
    currency_balance.set_hold(hold);
    raw_balance_map->emplace(currency_balance.currency_native(), currency_balance);
  }

  return true;
}

void LbankParser::ParseOpenOrderInfo() {
  /*
  {
    "result": true,
    "data": {
      "total": 1,
      "page_length": 200,
      "orders": [
        {
          "cummulativeQuoteQty": 0,
          "symbol": "btc_usdt",
          "executedQty": 0,
          "orderId": "0e8f87a7-6e08-45ff-abae-b3d647d8eeeb",
          "origQty": 1,
          "price": 1,
          "clientOrderId": "20230213test_id2",
          "origQuoteOrderQty": 1,
          "updateTime": 1676273846000,
          "time": 1676273846144,
          "type": "buy",
          "status": 0
        }
      ],
      "current_page": 1
    },
    "error_code": 0,
    "ts": 1676273878252
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseOpenOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsLbankSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseOpenOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("data") ||
      (doc["data"].HasMember("orders") && !doc["data"]["orders"].IsArray())) {
    LOG(ERROR) << "[ParseOpenOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  // empty open order not have orders
  if (!doc["data"].HasMember("orders")) {
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  std::string symbol;
  for (const auto& data : doc["data"]["orders"].GetArray()) {
    if (!data.HasMember("symbol")) {
      continue;
    }
    std::string native_symbol = data["symbol"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(order, data);
    symbol = native_symbol;
    product_order_bundle_found = true;
  }

  std::pair<int64_t, int64_t> page_info(1, 1);
  if (doc["data"].HasMember("current_page")) {
    page_info.first = stoll(doc["data"]["current_page"].GetString());
  }
  if (doc["data"].HasMember("total")) {
    page_info.second = stoll(doc["data"]["total"].GetString());
  }
  if (page_info.first == 1 && page_info.first < page_info.second && !symbol.empty()) {
    std::pair<std::string, int64_t> symbol_and_total(symbol, page_info.second);

    LOG(INFO) << "open order more than one page, need request remaining pages! symbol:" << symbol
              << " current_page:" << page_info.first << " total:" << page_info.second;
    mb_->PublishEvent("lbank_spot/query_open_orders", &symbol_and_total);
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void LbankParser::ParseOrderInfo(ProductOrderElement* order, const JsonValue& data) {
  order->set_exchange_type(ExchangeType::Lbank);
  order->set_market_type(MarketType::Spot);
  order->set_api_version("v2");
  order->set_exchange_order_id(data["orderId"].GetString());
  order->set_symbol(data["symbol"].GetString());
  order->set_price(stod(data["price"].GetString()));
  order->set_qty(std::fabs(stod(data["origQty"].GetString())));
  order->set_proc_order_id(GetProcOrderId(data));
  order->set_side(GetOrderSide(data["type"].GetString()));
  order->set_order_type(GetOrderType(data["type"].GetString()));
  order->set_timestamp(GetCurrentTimestamp());
  SetOrderStatus(order, data["status"].GetString());
}

void LbankParser::ParseHistoryOrderInfo() {
  /*
  {
    "result": true,
    "data": {
      "total": 1,
      "page_length": 200,
      "orders": [
        {
          "cummulativeQuoteQty": 0,
          "symbol": "btc_usdt",
          "executedQty": 0,
          "orderId": "89f88e13-6782-412c-b4b5-c7eb53f79efa",
          "origQty": 1,
          "price": 1,
          "origQuoteOrderQty": 1,
          "updateTime": 1676270699000,
          "time": 1676270662366,
          "type": "buy",
          "status": -1
        }
      ],
      "current_page": 1
    },
    "error_code": 0,
    "ts": 1676270709296
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseHistoryOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsLbankSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseHistoryOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("data") ||
      (doc["data"].HasMember("orders") && !doc["data"]["orders"].IsArray())) {
    LOG(ERROR) << "[ParseHistoryOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  // empty history order not have orders
  // {"result":true,"data":{"page_length":"200","current_page":"1"},"error_code":"0","ts":"1676348338233"}
  if (!doc["data"].HasMember("orders")) {
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc["data"]["orders"].GetArray()) {
    if (!data.HasMember("symbol")) {
      continue;
    }
    std::string native_symbol = data["symbol"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(order, data);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void LbankParser::ParseFillOrderInfo() {
  /*
  {
    "result": true,
    "data": [
      {
        "symbol": "btc_usdt",
        "quoteQty": 2.182381,
        "orderId": "eaeca3b8-2749-46db-9940-673e4962f7d2",
        "price": 21823.81,
        "qty": 0.0001,
        "commission": 0.0,
        "id": "4580697f59664bf183ee90bf6e572464",
        "time": 1676275520353,
        "isMaker": false,
        "isBuyer": true
      }
    ],
    "error_code": 0,
    "ts": 1676275550031
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsLbankSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  // empty fill order info not have data
  // {"result":true,"error_code":"0","ts":"1676352934742"}
  if (!doc.HasMember("data")) {
    return;
  }

  if (!doc["data"].IsArray()) {
    LOG(ERROR) << "[ParseFillOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : doc["data"].GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->Clear();
    ParseFillInfo(fill, data);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void LbankParser::ParseFillInfo(ProductFillElement* fill, const JsonValue& data) {
  fill->set_symbol(data["symbol"].GetString());
  fill->set_exchange_order_id(data["orderId"].GetString());
  fill->set_price(stod(data["price"].GetString()));
  fill->set_qty(std::fabs(stod(data["qty"].GetString())));
  fill->set_fill_id(data["id"].GetString());
  fill->set_fee(stod(data["commission"].GetString()));
  fill->set_fill_type(
      data["isMaker"].GetBool() ? coin::proto::FillType::MAKER_FILL_TYPE
                                : coin::proto::FillType::TAKER_FILL_TYPE);
  fill->set_fill_timestamp(GetCurrentTimestamp());
}

void LbankParser::ParseSubmitResponse() {
  /*
  {
    "result": true,
    "data": {
      "symbol": "btc_usdt",
      "custom_id": "20230213test_id4",
      "order_id": "eaeca3b8-2749-46db-9940-673e4962f7d2"
    },
    "error_code": 0,
    "ts": 1676275520249
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto exchange_error_code = GetErrorCode(json);
  auto result = GetResult(json);
  if (!exchange_error_code || !result) {
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto order_error_code = GetOrderErrorCode(exchange_error_code.value());
  resp->set_error_code(order_error_code);
  resp->set_exchange_error_code(std::to_string(exchange_error_code.value()));
  resp->set_exchange_error_msg(GetErrorMsg(exchange_error_code.value()));

  if (order_error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
    LOG(ERROR) << "[ParseSubmitResponse] Error response\n" << http_context->res;
    return;
  }

  if (!json.HasMember("data") || !json["data"].HasMember("order_id")) {
    resp->set_error_code(OrderErrorCode::ORDER_API_ERROR);
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(json);
    return;
  }

  resp->set_exchange_order_id(json["data"]["order_id"].GetString());
  resp->set_success(true);
}

void LbankParser::ParseCancelResponse() {
  /*
  {
    "result": true,
    "data": {
      "origClientOrderId": "20230213test_id2",
      "executedQty": 0.0,
      "price": 1.0,
      "origQty": 1.0,
      "tradeType": "buy",
      "status": 0
    },
    "error_code": 0,
    "ts": 1676273936547
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto exchange_error_code = GetErrorCode(json);
  auto result = GetResult(json);
  if (!exchange_error_code || !result) {
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto order_error_code = GetOrderErrorCode(exchange_error_code.value());
  resp->set_error_code(order_error_code);
  resp->set_exchange_error_code(std::to_string(exchange_error_code.value()));
  resp->set_exchange_error_msg(GetErrorMsg(exchange_error_code.value()));

  if (order_error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
    LOG(ERROR) << "[ParseCancelResponse] Error response\n" << http_context->res;
    return;
  }

  if (!json.HasMember("data") || !json["data"].HasMember("status")) {
    resp->set_error_code(OrderErrorCode::ORDER_API_ERROR);
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(json);
    return;
  }

  std::string status(json["data"]["status"].GetString());
  if (status == "-1" || status == "3") {
    resp->set_cancel_confirmed(true);
  }

  resp->set_success(true);
}

void LbankParser::OnParseListenKeyFail() {
  ++parse_listen_key_fail_time_;

  if (parse_listen_key_fail_time_ < 2) {
    return;
  }

  parse_listen_key_fail_time_ = 0;

  std::pair<bool, std::string> refresh_pair(true, "");
  mb_->PublishEvent("lbank/listen_key", &refresh_pair);
}

void LbankParser::ParseListenKey() {
  /*
  {
    "result": true,
    "data": "ca5c8ebb4cb38f73639e9800d4a8f16dddb88618ee147d10211b0660a0a86009",
    "error_code": 0,
    "ts": 1676277594110
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseListenKey] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    OnParseListenKeyFail();
    return;
  }

  LOG(INFO) << "lbank/spot: " << JsonToString(document);
  if (IsLbankSpotErrMessage(document)) {
    LOG(ERROR) << "[ParseListenKey] Error message\n" << JsonToString(document);
    OnParseListenKeyFail();
    return;
  }

  parse_listen_key_fail_time_ = 0;
  // refresh key only have result
  // {"result":true,"error_code":"0","ts":"1676346234592"}
  if (!document.HasMember("data")) {
    return;
  }

  if (!mb_) {
    return;
  }

  std::pair<bool, std::string> key_pair(false, document["data"].GetString());
  mb_->PublishEvent("lbank/listen_key", &key_pair);
}

int32_t LbankParser::ParseErrorMessage(const std::shared_ptr<AsioHttpContext>& http_context) {
  const auto http_code = http_context->res.result_int();
  if (http_code == 403) {
    LOG(ERROR) << "[ParseErrorMessage] Access denied\n" << msg_->DebugString();
  } else if (http_code == 404) {
    LOG(ERROR) << "[ParseErrorMessage] Request path not found\n" << msg_->DebugString();
  } else if (http_code == 429) {
    HitRateLimit(__func__);
  }

  return http_code;
}

void LbankParser::ParseWsBalanceInfoList(const JsonDocument& document) {
  /*
  {
    "data":{
      "asset":"497.817619",
      "assetCode":"usdt",
      "free":"496.817619",
      "freeze":"1",
      "time":1676279224756,
      "type":"ORDER_CREATE"
    },
    "SERVER":"V2",
    "type":"assetUpdate",
    "TS":"2023-02-13T17:07:04.758"
  }
  */
  if (!is_account_balance_initialized_) {
    return;
  }

  if (!document.HasMember("data")) {
    LOG(ERROR) << "[ParseWsBalanceInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  const auto& data = document["data"];
  if (!data.HasMember("asset") || !data.HasMember("assetCode") || !data.HasMember("free") ||
      !data.HasMember("freeze")) {
    LOG(ERROR) << "[ParseWsBalanceInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Lbank");
  acc_balance->set_market_type("Spot");

  auto* currency = symcache_->GetCurrencyByNative(data["assetCode"].GetString());
  auto* balance = acc_balance->add_each_balance();
  balance->set_currency(currency->currency());
  balance->set_currency_native(currency->native_currency());
  balance->set_total(stod(data["asset"].GetString()));
  balance->set_available(stod(data["free"].GetString()));
  balance->set_hold(stod(data["freeze"].GetString()));

  result_.set_type(MsgType::ACCOUNT_BALANCE);
}

void LbankParser::ParseWsOrderInfoList(const JsonDocument& document) {
  /*
  {
    "SERVER":"V2",
    "orderUpdate":{
      "accAmt":"0",
      "amount":"0",
      "avgPrice":"0",
      "customerID":"20230213test_id6",
      "orderAmt":"1",
      "orderPrice":"1",
      "orderStatus":0,
      "price":"1",
      "remainAmt":"1",
      "role":"taker",
      "symbol":"btc_usdt",
      "type":"buy",
      "updateTime":1676279224755,
      "uuid":"e61da633-544a-4150-aa55-6a8e95dd30c9",
      "volumePrice":"0"
    },
    "type":"orderUpdate",
    "pair":"btc_usdt",
    "TS":"2023-02-13T17:07:04.757"
  }
  */
  if (!document.HasMember("orderUpdate")) {
    LOG(ERROR) << "[ParseWsOrderInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();

  const auto& order_update = document["orderUpdate"];
  auto order = product_order_bundle->add_each_order();
  order->set_exchange_type(ExchangeType::Lbank);
  order->set_market_type(MarketType::Spot);
  order->set_api_version("v2");
  order->set_exchange_order_id(order_update["uuid"].GetString());
  order->set_symbol(order_update["symbol"].GetString());
  order->set_price(stod(order_update["orderPrice"].GetString()));
  order->set_qty(std::fabs(stod(order_update["orderAmt"].GetString())));
  order->set_proc_order_id(GetWsProcOrderId(order_update));
  order->set_side(GetOrderSide(order_update["type"].GetString()));
  order->set_order_type(GetOrderType(order_update["type"].GetString()));
  order->set_timestamp(GetCurrentTimestamp());
  SetOrderStatus(order, order_update["orderStatus"].GetString());

  result_.set_type(MsgType::ORDER_STATUS);
}

void LbankParser::ParseWsTradeInfoList(const JsonDocument& document) {
  /*
  {
    "SERVER":"V2",
    "orderUpdate":{
      "accAmt":"0.0001",
      "amount":"0.0001",
      "avgPrice":"22165.25",
      "customerID":"1676369916612299400",
      "orderAmt":"0.0001",
      "orderPrice":"22165.25",
      "orderStatus":2,
      "price":"22165.25",
      "remainAmt":"0",
      "role":"maker",
      "symbol":"btc_usdt",
      "txUuid":"fdc0c3617b614c8fa009966cbb9b12fa",
      "type":"sell",
      "updateTime":1676414914368,
      "uuid":"f22ee66c-3b30-4e75-a652-cfafd9514d67",
      "volumePrice":"2.216525"
    },
    "type":"orderUpdate",
    "pair":"btc_usdt",
    "TS":"2023-02-15T06:48:34.383"
  }
  */
  if (!document.HasMember("orderUpdate")) {
    LOG(ERROR) << "[ParseWsTradeInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  const auto& order_update = document["orderUpdate"];
  if (!order_update.HasMember("txUuid")) {
    // just a order info, not contains trade info
    return;
  }

  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();

  auto fill = product_fill_bundle->add_each_fill();
  fill->set_symbol(order_update["symbol"].GetString());
  fill->set_price(stod(order_update["price"].GetString()));
  fill->set_qty(std::fabs(stod(order_update["amount"].GetString())));
  fill->set_side(GetOrderSide(order_update["type"].GetString()));
  fill->set_fill_timestamp(GetCurrentTimestamp());
  fill->set_fill_id(order_update["txUuid"].GetString());
  fill->set_exchange_order_id(order_update["uuid"].GetString());
  fill->set_proc_order_id(GetWsProcOrderId(order_update));
  fill->set_fill_type(
      std::string(order_update["role"].GetString()) == "maker"
          ? coin::proto::FillType::MAKER_FILL_TYPE
          : coin::proto::FillType::TAKER_FILL_TYPE);

  result_.set_type(MsgType::ORDER_FILL);
}

void LbankParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  DLOG(INFO) << "OG ws msg: " << JsonToString(document);
  if (IsOrderUpdate(document)) {
    ParseWsOrderInfoList(document);
    ParseWsTradeInfoList(document);
  } else if (IsBalanceUpdate(document)) {
    ParseWsBalanceInfoList(document);
  }
}

auto LbankParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  CHECK(msg.type == OrderSubTopicId::REST_MESSAGE);

  ParseErrorMessage(msg.http_context);

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseAccountBalance();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseOpenOrderInfo();
      break;
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseHistoryOrderInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseFillOrderInfo();
      break;
    case OrderSubTopicId::LISTEN_KEY:
      ParseListenKey();
      break;
    default: {
      LOG(ERROR) << "[ParseRawMessage] Unknown query type\n" << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::lbank::order_v2
